﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Workbench.Models;
using Workbench.Persistance;
using Workbench.Shared;

namespace Workbench.Operations
{
    public static class ProjectOperations
    {
        #region Project members

        public static void CreateAndOpenProject(string projectDbPath, string projectName)
        {
            DbUtility.CreateDatabase(projectDbPath);
            DBContext.Current.Initialize(projectDbPath);
            DBContext.Current.CreateTablesAndIndexes1();
            DBContext.Current.CreateTablesAndIndexes2();
            using (var buildRepo = DBContext.Current.GetBuildRepository())
            {
                try
                {
                    var rootDirectory = new ProjectDirectoryModel()
                    {
                        Name = projectName,
                    };
                    buildRepo.BeginTransaction();
                    try
                    {
                        buildRepo.PersistProjectDirectory(rootDirectory);
                        buildRepo.Commit();
                    }
                    catch
                    {
                        buildRepo.Rollback();
                        throw;
                    }
                }
                catch (Exception ex)
                {
                    LoggingService.LogError(ex);
                    return;
                }
            }
            OpenProject(projectDbPath);
        }

        public static void OpenProject(string projectDbPath)
        {
            DBContext.Current.Initialize(projectDbPath);
        }

        public static bool HasOpenedProject
        {
            get
            {
                return DBContext.Current.IsInitialized;
            }
        }

        public static string GetOpenedProjectFilePath()
        {
            if (HasOpenedProject)
                return DBContext.Current.projectDbFilePath;
            else
                return null;
        }

        public static void CloseProject()
        {
            DBContext.Current.UnInitialize();
        }

        #endregion Project members

        #region Project Structure API

        public static ProjectDirectoryModel GetRootDirectory()
        {
            using (var m = new TimeMeasure("ProjectOperations.GetRootDirectory"))
            {
                using (var repo = DBContext.Current.GetBuildRepository())
                {
                    try
                    {
                        var result = repo.GetRootDirectoryModel();
                        return result;
                    }
                    catch (Exception ex)
                    {
                        LoggingService.LogError(ex);
                        return null;
                    }
                }
            }
        }

        public static ProjectDirectoryModel AddDirectory(string newDirectoryName, long parentDirectoryId)
        {
            using (var buildrepo = DBContext.Current.GetBuildRepository())
            {
                try
                {
                    var model = new ProjectDirectoryModel()
                    {
                        Name = newDirectoryName,
                        ParentDirectoryId = parentDirectoryId
                    };
                    buildrepo.BeginTransaction();
                    try
                    {
                        buildrepo.PersistProjectDirectory(model);
                        buildrepo.Commit();
                    }
                    catch
                    {
                        buildrepo.Rollback();
                        throw;
                    }
                    return model;
                }
                catch (Exception ex)
                {
                    LoggingService.LogError(ex);
                    buildrepo.Rollback();
                    return null;
                }
            }
        }

        public static ProjectDirectoryModel AddExistingDirectory(string existingDirectoryPath, long parentDirectoryId)
        {
            using (var repo = DBContext.Current.GetBuildRepository())
            {
                try
                {
                    var directoryName = Path.GetFileName(existingDirectoryPath);
                    var model = new ProjectDirectoryModel()
                    {
                        Name = directoryName,
                        ParentDirectoryId = parentDirectoryId
                    };
                    repo.BeginTransaction();
                    try
                    {
                        repo.PersistProjectDirectory(model);
                        repo.Commit();
                    }
                    catch
                    {
                        repo.Rollback();
                        throw;
                    }

                    foreach (var subDirectoryPath in Directory.EnumerateDirectories(existingDirectoryPath))
                    {
                        var directory = AddExistingDirectory(subDirectoryPath, model.RowId);
                        model.Children.Add(directory);
                        directory.ParentDirectory = model;
                    }

                    foreach (var filePath in Directory.EnumerateFiles(existingDirectoryPath, "*.txt", SearchOption.TopDirectoryOnly))
                    {
                        var file = AddExistingFile(filePath, model.RowId);
                        file.ParentDirectory = model;
                        model.Children.Add(file);
                    }
                    return model;
                }
                catch (Exception ex)
                {
                    LoggingService.LogError(ex);
                    return null;
                }
            }
        }

        public static ProjectFileModel AddExistingFileContent(string fileName, string content, long parentDirectoryId)
        {
            using (var buildrepo = DBContext.Current.GetBuildRepository())
            {
                try
                {

                    var fileContent = new FileContentModel()
                    {
                        Content = content,
                    };
                    var file = new ProjectFileModel()
                    {
                        Name = fileName,
                        ContextIncluded = true,
                        FileContent = fileContent,
                        ParentDirectoryId = parentDirectoryId
                    };
                    var textRepo = DBContext.Current.GetTextRepository();

                    var uniqueWords = textRepo.GetAllUniqueWords(x => x.Content);
                    var uniqueWordsTS = uniqueWords.Select(x => new TSUniqueWord() { Content = x.Key })
                                                .ToDictionary(x => x.Content);

                    var result = TextSegmentationService.Process(content, uniqueWordsTS);

                    var paragraphs = new Dictionary<TSParagraph, ParagraphModel>();
                    result.Paragraphs.ForEach(x => paragraphs.Add(x, new ParagraphModel()
                    {
                        FileId = file.RowId,
                        File = file,
                        Length = x.Length,
                        StartPosition = x.StartPosition
                    }));

                    var sentences = new Dictionary<TSSentence, SentenceModel>();
                    result.Sentences.ForEach(x => sentences.Add(x, new SentenceModel()
                    {
                        Paragraph = paragraphs[x.Paragraph],
                        Length = x.Length,
                        StartPosition = x.StartPosition
                    }));

                    var newUniqueWords = new Dictionary<TSUniqueWord, UniqueWordModel>();
                    result.NewUniqueWords.ForEach(x => newUniqueWords.Add(x, new UniqueWordModel()
                    {
                        Content = x.Content
                    }));

                    var words = new List<WordModel>();
                    result.Words.ForEach(x =>
                    {
                        UniqueWordModel uw;
                        if (!uniqueWords.TryGetValue(x.UniqueWord.Content, out uw))
                        {
                            if (!newUniqueWords.TryGetValue(x.UniqueWord, out uw))
                            {
                                Debug.Fail("neeee!");
                                uw = new UniqueWordModel() { Content = x.UniqueWord.Content };
                                newUniqueWords.Add(x.UniqueWord, uw);
                            }
                        }
                        words.Add(new WordModel()
                        {
                            Sentence = sentences[x.Sentence],
                            UniqueWord = uw
                        });
                    });

                    var statistics = new FileStatisticsModel()
                    {
                        File = file,
                        MaximumNumberOfSentencesInParagraph = result.MaximumNumberOfSentencesInParagraph,
                        MaximumNumberOfWordsInSentence = result.MaximumNumberOfWordsInSentence,
                        Paragraphs = result.Paragraphs.Count,
                        Sentences = result.Sentences.Count,
                        TextLengthInRunningWords = result.Words.Count,
                        DifferentForms = result.DifferentForms,
                        AverageLengthOfParagraphInSentences = result.AverageLengthOfParagraphInSentences,
                        AverageLengthOfParagraphInWords = result.AverageLengthOfParagraphInWords,
                        AverageLengthOfSentenceInWords = result.AverageLengthOfSentenceInWords
                    };

                    buildrepo.BeginTransaction();
                    try
                    {
                        buildrepo.PersistFileContent(fileContent);
                        buildrepo.PersistFile(file);
                        buildrepo.PersistParagraphs(paragraphs.Values);
                        buildrepo.PersistSentences(sentences.Values);
                        buildrepo.PersistUniqueWords(newUniqueWords.Values);
                        buildrepo.PersistWords(words);
                        buildrepo.PersistFileStatistics(statistics);
                        buildrepo.Commit();
                    }
                    catch
                    {
                        buildrepo.Rollback();
                        throw;
                    }

                    return file;
                }
                catch (Exception ex)
                {
                    LoggingService.LogError(ex);
                    return null;
                }
            }
        }

        public static ProjectFileModel AddExistingFile(string existingFilePath, long parentDirectoryId)
        {
            var fileName = Path.GetFileName(existingFilePath);
            var content = File.ReadAllText(existingFilePath).Replace("\r\n", "\n");
            return AddExistingFileContent(fileName, content, parentDirectoryId);
        }

        public static FileContentModel GetFileContentById(long rowId)
        {
            using (var buildRepo = DBContext.Current.GetBuildRepository())
            {
                try
                {
                    var model = buildRepo.GetFileContentById(rowId);
                    return model;
                }
                catch (Exception ex)
                {
                    LoggingService.LogError(ex);
                    return null;
                }
            }
        }

        public static void RemoveProjectDirectory(long rowId)
        {
            try
            {
                using (var buildrepo = DBContext.Current.GetBuildRepository())
                {
                    buildrepo.BeginTransaction();
                    try
                    {
                        var directory = buildrepo.GetDirectoryById(rowId);
                        buildrepo.DeleteProjectItem(directory);
                        buildrepo.Commit();
                    }
                    catch
                    {
                        buildrepo.Rollback();
                        throw;
                    }
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
            }
        }

        public static void RemoveProjectFile(long rowId)
        {
            try
            {
                using (var buildrepo = DBContext.Current.GetBuildRepository())
                {
                    buildrepo.BeginTransaction();
                    try
                    {
                        var directory = buildrepo.GetFileById(rowId);
                        buildrepo.DeleteProjectItem(directory);
                        buildrepo.Commit();
                    }
                    catch
                    {
                        buildrepo.Rollback();
                        throw;
                    }
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
                return;
            }
        }

        public static void UpdateFileContextInclude(long rowId, bool newValue)
        {
            try
            {
                using (var repo = DBContext.Current.GetBuildRepository())
                {
                    repo.BeginTransaction();
                    try
                    {
                        var file = repo.GetFileById(rowId);
                        file.ContextIncluded = newValue;
                        repo.PersistFile(file);
                        repo.Commit();
                    }
                    catch
                    {
                        repo.Rollback();
                        throw;
                    }
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
                return;
            }
        }

        public static Task UpdateFileContextIncludeAsync(long rowId, bool newValue)
        {
            return Task.Run(() =>
            {
                UpdateFileContextInclude(rowId, newValue);
            });
        }

        public static QuantitativeMeasures GetQuantitativeMeasures(IEnumerable<ProjectFileModel> contextFiles)
        {
            try
            {
                using (var repository = DBContext.Current.GetBuildRepository())
                {
                    return repository.GetQuantitativeMeasures(contextFiles);
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
                return null;
            }
        }
        public static Task<QuantitativeMeasures> GetQuantitativeMeasuresAsync(IEnumerable<ProjectFileModel> contextFiles)
        {
            return Task<QuantitativeMeasures>.Run(() =>
            {
                return GetQuantitativeMeasures(contextFiles);
            }, CancellationToken.None);
        }

        #endregion

        #region POS Types

        public static List<POSTypeModel> GetPOSTypes()
        {
            try
            {
                using (var repo = DBContext.Current.GetBuildRepository())
                {
                    var result = repo.GetPOSTypes();
                    return result;
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
                return null;
            }
        }

        public static List<POSTypeModel> GetPOSTypesWithStatistics(IEnumerable<ProjectFileModel> filesInContext)
        {
            try
            {
                using (var repo = DBContext.Current.GetBuildRepository())
                {
                    return repo.GetPOSTypesWithStatistics(filesInContext);
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
                return null;
            }
        }

        public static Task<List<POSTypeModel>> GetPOSTypesWithStatisticsAsync(IEnumerable<ProjectFileModel> filesInContext)
        {
            return Task.Run<List<POSTypeModel>>(() =>
            {
                return GetPOSTypesWithStatistics(filesInContext);
            });
        }

        public static POSTypeModel AddPOSType(string Name, POSTypeModel parent)
        {
            try
            {
                using (var buildrepo = DBContext.Current.GetBuildRepository())
                {
                    var model = new POSTypeModel() { Name = Name };
                    if (parent != null)
                    {
                        model.ParentPOSType = parent;
                        model.ParentPOSTypeId = parent.RowId;
                    }
                    buildrepo.BeginTransaction();
                    try
                    {
                        buildrepo.PersistPOSType(model);
                        buildrepo.Commit();
                    }
                    catch
                    {
                        buildrepo.Rollback();
                        throw;
                    }
                    if (model.ParentPOSType != null)
                    {
                        parent.Children.Add(model);
                    }
                    return model;
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
                return null;
            }
        }

        public static void RemovePOSType(POSTypeModel posType)
        {
            try
            {
                using (var buildrepo = DBContext.Current.GetBuildRepository())
                {
                    buildrepo.BeginTransaction();
                    try
                    {
                        buildrepo.DeletePOSType(posType);
                        buildrepo.Commit();
                    }
                    catch
                    {
                        buildrepo.Rollback();
                        throw;
                    }
                    if (posType.ParentPOSType != null)
                    {
                        posType.ParentPOSType.Children.Remove(posType);
                    }
                }
            }
            catch (Exception ex)
            {
                LoggingService.LogError(ex);
            }
        }

        public static string SerializePOSTypesToText(List<POSTypeModel> types)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var item in types)
            {
                writePOSType(sb, item, 0);
            }
            return sb.ToString();
        }

        private static void writePOSType(StringBuilder sb, POSTypeModel model, int lvl)
        {
            sb.Append(new string('\t', lvl));
            sb.AppendLine(model.Name);
            foreach (var item in model.Children)
                writePOSType(sb, item, lvl + 1);
        }

        private static void flatten(IEnumerable<POSTypeModel> posTypes, List<POSTypeModel> flattenedPOSTypes)
        {
            foreach (var posType in posTypes)
            {
                flattenedPOSTypes.Add(posType);
                flatten(posType.Children, flattenedPOSTypes);
            }
        }
        public static List<POSTypeModel> LoadPOSTypesFromFile(string fileNameWithoutExtension)
        {
            var text = File.ReadAllText(Path.Combine(POSTypeSets, Path.ChangeExtension(fileNameWithoutExtension, "txt")));
            var POStypes = LoadPOSTypesFromText(text);

            var flatennedPOSTypes = new List<POSTypeModel>();
            flatten(POStypes, flatennedPOSTypes);

            using(var buildRepo = DBContext.Current.GetBuildRepository())
            {
                try
                {
                    buildRepo.BeginTransaction();

                    foreach(var item in flatennedPOSTypes)
                    {
                        if(item.ParentPOSType != null)
                        {
                            item.ParentPOSTypeId = item.ParentPOSType.RowId;
                        }

                        buildRepo.PersistPOSType(item);
                    }
                    buildRepo.Commit();
                }
                catch
                {
                    buildRepo.Rollback();
                }
            }
            return POStypes;
        }

        public static List<POSTypeModel> LoadPOSTypesFromText(string text)
        {
            text = text.Replace("\r\n", "\n");
            POSTypeModel all = new POSTypeModel();

            var lines = text.Split("\n".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            lines = lines.Where(x => x.Trim().Length != 0).ToArray();
            if (lines.Length == 0)
                return new List<POSTypeModel>();

            int indentLvl;
            string posTypeString;

            //Process the first
            countIndentation(lines[0], out indentLvl, out posTypeString);
            POSTypeModel current = new POSTypeModel();
            current.Name = posTypeString;
            current.ParentPOSType = all;
            all.Children.Add(current);
            var lvls = new Dictionary<POSTypeModel, int>(POSTypeModel.EqualsByPath);
            lvls[all] = -1;
            lvls[current] = indentLvl;

            for (int i = 1; i < lines.Length; i++)
            {
                countIndentation(lines[i], out indentLvl, out posTypeString);
                if (indentLvl < lvls[current]) //pop until reached
                {
                    do
                    {
                        current = current.ParentPOSType;
                        if (current == null)
                        {
                            throw new Exception("Malformatted nesting in Part-of-speech types file");
                        }
                    } while (indentLvl < lvls[current]);
                }

                if (indentLvl >= lvls[current])
                {
                    POSTypeModel parent = null;
                    if (lvls[current] == indentLvl) // sibling
                    {
                        parent = current.ParentPOSType;
                    }
                    else // child
                    {
                        parent = current;
                    }
                    var newPOSType = new POSTypeModel()
                    {
                        Name = posTypeString,
                        ParentPOSType = parent,
                    };
                    lvls[newPOSType] = indentLvl;
                    parent.Children.Add(newPOSType);
                    current = newPOSType;
                }
            }
            return all.Children.ToList();
        }

        private static void countIndentation(string line, out int indentLvl, out string posTypeString)
        {
            line = line.Replace("\t", "    ");
            for (int i = 0; i < line.Length; i++)
            {
                if (char.IsWhiteSpace(line[i])) continue;
                indentLvl = i;
                posTypeString = line.Substring(i);
                return;
            }
            throw new Exception("Unexpected empty line was found in a part-of-speech file");
        }

        #endregion POS Types


        private const string POSTypeSets = @"POSTypeSets";
        public static string[] GetAvailablePOSTypeSets(string path = POSTypeSets)
        {
            if (!Directory.Exists(path))
            {
                Debug.Fail(string.Format("{0} does not exists", path));
                return new string[0];
            }

            return Directory.EnumerateFiles(path, "*.txt")
                .Select(x => Path.GetFileNameWithoutExtension(x))
                .ToArray();
        }
    }
}
